from email import message
from multiprocessing import context
from turtle import title
from django.views.generic import ListView, DetailView
from django import template
from django.db.models.fields import SlugField
from django.shortcuts import redirect, render, get_object_or_404
from django.http import HttpResponse
from django.utils.text import slugify
from markdown.extensions.toc import TocExtension
from GO_lww.models import Post, Category, Tag
import markdown
import re
from comments.forms import CommentForm
from django.contrib import messages
from django.db.models import Q

# 首页
# def index(request):
#     # 排序依据的字段是 created_time，即文章的创建时间。- 号表示逆序，如果不加 - 则是正序。
#     post_list = Post.objects.all().order_by('-created_time')
#     return render(request,
#                   'david/index.html',
#                   context={'post_list': post_list})

# 详情页
# def detail(request, pk):
#     # post = get_object_or_404(Post, pk=pk)
#     # # 实例化 markdown.Markdown 对象
#     # md = markdown.Markdown(extensions=[
#     #     'markdown.extensions.extra',
#     #     'markdown.extensions.codehilite',
#     #     # 'markdown.extensions.toc',
#     #     TocExtension(slugify=slugify),
#     # ])
#     # # md = markdown.markdown(post.body,
#     # #                               extensions=[
#     # #                                   'markdown.extensions.extra',
#     # #                                   'markdown.extensions.codehilite',
#     # #                                   'markdown.extensions.toc',
#     # #                               ])
#     # post.body = md.convert(post.body)
#     # # 正则表达式匹配生成的目录中包裹在 ul 标签中的内容，如果不为空，说明有目录，就把 ul 标签中的值提取出来（目的是只要包含目录内容的最核心部分，多余的 HTML 标签结构丢掉）赋值给 post.toc；
#     # # 否则，将 post 的 toc 置为空字符串，然后在模板中通过判断 post.toc 是否为空，来决定是否显示侧栏目录
#     # m = re.search(r'<div class="toc">\s*<ul>(.*)</ul>\s*</div>', md.toc, re.S)
#     # post.toc = m.group(1) if m is not None else ''
#     # return render(request, 'david/detail.html', context={'post': post})

#     post = get_object_or_404(Post, pk=pk)

#     # 阅读量 +1
#     post.increase_views()

#     post.body = markdown.markdown(post.body,
#                                   extensions=[
#                                       'markdown.extensions.extra',
#                                       'markdown.extensions.codehilite',
#                                       'markdown.extensions.toc',
#                                   ])
#     # 记得在顶部导入 CommentForm
#     form = CommentForm()
#     # 获取这篇 post 下的全部评论
#     comment_list = post.comment_set.all()
#     comment_count = comment_list.count()

#     # 将文章、表单、以及文章下的评论列表作为模板变量传给 detail.html 模板，以便渲染相应数据。
#     context = {
#         'post': post,
#         'form': form,
#         'comment_list': comment_list,
#         'comment_count': comment_count
#     }
#     return render(request, 'david/detail.html', context=context)

# 归档页面
# def archive(request, year, month):
#     post_list = Post.objects.filter(
#         created_time__year=year,
#         created_time__month=month).order_by('-created_time')
#     return render(request,
#                   'david/index.html',
#                   context={'post_list': post_list})

# 分类页面
# def category(request, pk):
#     cate = get_object_or_404(Category, pk=pk)
#     post_list = Post.objects.filter(category=cate).order_by('-created_time')
#     return render(request,
#                   'david/index.html',
#                   context={'post_list': post_list})

# 标签页面
# def tag(request, pk):
#     t = get_object_or_404(Tag, pk=pk)
#     post_list = Post.objects.filter(tags=t).order_by('-created_time')
#     return render(request,
#                   'david/index.html',
#                   context={'post_list': post_list})


def search(request):
    q = request.GET.get('q')

    if not q:
        error_msg = "请输入搜索关键词"
        message.add_message(request,
                            messages.ERROR,
                            error_msg,
                            extra_tags="danger")
        return redirect("david:index")

    post_list = Post.objects.filter(
        Q(title__icontains=q) | Q(body__icontains=q))
    return render(request, "david/index.html", {"post_list": post_list})


# index 类视图
# IndexView 的功能是从数据库中获取文章（Post）列表，ListView 就是从数据库中获取某个模型列表数据的，所以 IndexView 继承 ListView。
class IndenxView(ListView):

    # 将 model 指定为 Post
    model = Post
    # 指定这个视图渲染的模板
    template_name = 'david/index.html'
    # 指定获取的模型列表数据保存的变量名，这个变量会被传递给模板。
    context_object_name = 'post_list'
    # 指定paginate_by 属性后开启分页功能，其值代表每一页包含多少篇 文章
    paginate_by = 2

    def get_context_data(self, **kwargs):
        """
        在视图函数中将模板变量传递给模板是通过给 render 函数的 context 参数传递一个字典实现的，
        例如 render(request, 'blog/index.html', context={'post_list': post_list})，
        这里传递了一个 {'post_list': post_list} 字典给模板。
        在类视图中，这个需要传递的模板变量字典是通过 get_context_data 获得的，
        所以我们复写该方法，以便我们能够自己再插入一些我们自定义的模板变量进去。
        """

        # 首先获得父类生成的传递给模板的字典
        context = super().get_context_data(**kwargs)

        # 父类生成的字典中已有 paginator、page_obj、is_paginated 这三个模板变量，
        # paginator 是 Paginator 的一个实例，
        # page_obj 是 Page 的一个实例，
        # is_paginated 是一个布尔变量，用于指示是否已分页。
        # 例如如果规定每页 10 个数据，而本身只有 5 个数据，其实就用不着分页，此时 is_paginated=False。
        # 由于 context 是一个字典，所以调用 get 方法从中取出某个键对应的值。
        paginator = context.get('paginator')
        page = context.get('page_obj')
        is_paginated = context.get('is_paginated')

        # 调用自己写的 pagination_data 方法获得显示分页导航条需要的数据
        pagination_data = self.pagination_data(paginator, page, is_paginated)

        # 将分页导航条的模板变量更新到 context 中， 注意pagination_data 方法返回的也是一个字典
        context.update(pagination_data)

        return context

    def pagination_data(self, paginator, page, is_paginated):
        if not is_paginated:
            # 如果没有分页， 则无需显示分页导航条， 不用任何分页导航条的数据，因此返回一个空的字典
            return {}

        # 当前页左边连续的页码号，初始值为空
        left = []

        # 当前页右边连续的页码号，初始值为空
        right = []

        # 标示第一页页码是否需要显示省略号
        left_has_more = False

        # 标示最后一页页码是否需要显示省略号
        right_has_more = False

        # 标示是否需要显示第 1 页的页码号。
        # 因为如果当前页左边的连续页码号中已经含有第 1 页的页码号，此时就无需再显示第 1 页的页码号，
        # 其它情况下第一页的页码是始终需要显示的。
        # 初始值为 False
        first = False

        # 标示是否需要显示最后一页的页码号。
        # 需要此指示变量的理由和上面相同。
        last = False

        # 获得用户当前请求的页码号
        page_number = page.number

        # 获得分页后的总页数
        total_pages = paginator.num_pages

        # 获得整个分页页码列表，比如分了四页，那么就是 [1, 2, 3, 4]
        page_range = paginator.page_range

        if page_number == 1:
            # 如果用户请求的是第一页的数据，那么当前页左边的不需要数据，因此 left=[]（已默认为空）。
            # 此时只要获取当前页右边的连续页码号，
            # 比如分页页码列表是 [1, 2, 3, 4]，那么获取的就是 right = [2, 3]。
            # 注意这里只获取了当前页码后连续两个页码，你可以更改这个数字以获取更多页码。
            right = page_range[page_number:page_number + 2]

            # 如果最右边的页码号比最后一页的页码号减去 1 还要小，
            # 说明最右边的页码号和最后一页的页码号之间还有其它页码，因此需要显示省略号，通过 right_has_more 来指示。
            if right[-1] < total_pages - 1:
                right_has_more = True

            # 如果最右边的页码号比最后一页的页码号小，说明当前页右边的连续页码号中不包含最后一页的页码
            # 所以需要显示最后一页的页码号，通过 last 来指示
            if right[-1] < total_pages:
                last = True

        elif page_number == total_pages:
            # 如果用户请求的是最后一页的数据，那么当前页右边就不需要数据，因此 right=[]（已默认为空），
            # 此时只要获取当前页左边的连续页码号。
            # 比如分页页码列表是 [1, 2, 3, 4]，那么获取的就是 left = [2, 3]
            # 这里只获取了当前页码后连续两个页码，你可以更改这个数字以获取更多页码。
            left = page_range[(page_number -
                               3) if (page_number - 3) > 0 else 0:page_number -
                              1]

            # 如果最左边的页码号比第 2 页页码号还大，
            # 说明最左边的页码号和第 1 页的页码号之间还有其它页码，因此需要显示省略号，通过 left_has_more 来指示。
            if left[0] > 2:
                left_has_more = True

            # 如果最左边的页码号比第 1 页的页码号大，说明当前页左边的连续页码号中不包含第一页的页码，
            # 所以需要显示第一页的页码号，通过 first 来指示
            if left[0] > 1:
                first = True
        else:
            # 用户请求的既不是最后一页，也不是第 1 页，则需要获取当前页左右两边的连续页码号，
            # 这里只获取了当前页码前后连续两个页码，你可以更改这个数字以获取更多页码。
            left = page_range[(page_number -
                               3) if (page_number - 3) > 0 else 0:page_number -
                              1]
            right = page_range[page_number:page_number + 2]

            # 是否需要显示最后一页和最后一页前的省略号
            if right[-1] < total_pages - 1:
                right_has_more = True
            if right[-1] < total_pages:
                last = True

            # 是否需要显示第 1 页和第 1 页后的省略号
            if left[0] > 2:
                left_has_more = True
            if left[0] > 1:
                first = True

        data = {
            'left': left,
            'right': right,
            'left_has_more': left_has_more,
            'right_has_more': right_has_more,
            'first': first,
            'last': last,
        }

        return data


# category 类试图
class CategoryView(ListView):
    model = Post
    template_name = 'david/index.html'
    context_object_name = 'post_list'

    def get_queryset(self):
        cate = get_object_or_404(Category, pk=self.kwargs.get('pk'))
        return super(CategoryView, self).get_queryset().filter(category=cate)


# tag 类试图
class TagView(ListView):
    model = Post
    template_name = 'david/index.html'
    context_object_name = 'post_list'

    def get_queryset(self):
        t = get_object_or_404(Tag, pk=self.kwargs.get('pk'))
        return super(TagView, self).get_queryset().filter(tags=t)


# archive 类视图
class ArchiveView(ListView):
    model = Post
    template_name = 'david/index.html'
    context_object_name = 'post_list'

    def get_queryset(self):
        return super(ArchiveView, self).get_queryset().filter(
            created_time__year=self.kwargs.get('year'),
            created_time__month=self.kwargs.get('month'))


# 详情页 类试图
class PostDetailView(DetailView):
    model = Post
    template_name = 'david/detail.html'
    context_object_name = 'post'

    def get(self, request, *args, **kwargs):
        # 覆写 get 方法的目的是因为每当文章被访问一次，就得将文章阅读量 +1
        # get 方法返回的是一个 HttpResponse 实例
        # 之所以需要先调用父类的 get 方法，是因为只有当 get 方法被调用后，
        # 才有 self.object 属性，其值为 Post 模型实例，即被访问的文章 post
        response = super(PostDetailView, self).get(request, *args, **kwargs)

        # 将文章阅读量 +1
        # 注意 self.object 的值就是被访问的文章 post
        self.object.increase_views()

        # 视图必须返回一个 HttpResponse 对象
        return response

    def get_object(self, queryset=None):
        # 覆写 get_object 方法的目的是因为需要对 post 的 body 值进行渲染
        post = super(PostDetailView, self).get_object(queryset=None)
        # 覆写 get_object 方法的目的是因为需要对 post 的 body 值进行渲染
        md = markdown.Markdown(extensions=[
            'markdown.extensions.extra',
            'markdown.extensions.codehilite',
            'markdown.extensions.toc',
        ])
        post.body = md.convert(post.body)
        post.toc = md.toc
        return post

    def get_context_data(self, **kwargs):
        # 覆写 get_context_data 的目的是因为除了将 post 传递给模板外（DetailView 已经帮我们完成），
        # 还要把评论表单、post 下的评论列表传递给模板。
        context = super(PostDetailView, self).get_context_data(**kwargs)
        form = CommentForm()
        comment_list = self.object.comment_set.all()
        comment_count = comment_list.count()
        context.update({
            'form': form,
            'comment_list': comment_list,
            'comment_count': comment_count
        })
        return context
